package ig.finanzas;

//import ig.metrica.ejb.html.GenerarHTML;
import ig.metrica.TasaFactorDescuento;
import ig.util.excepciones.DatoInvalidoExcepcion;
import ig.util.excepciones.LogicaExcepcion;
import java.io.Serializable;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;

/**
 * Modela una coleccion de flujos y los metodos que se pueden operar sobre
 * estos.
 * @author lfgarcia
 */
public class ManejadorFlujos implements Serializable {

    static final long serialVersionUID = 01;
    /**
     * @poseidon-type  Flujo
     * @uml.property  name="flujos"
     * @uml.associationEnd  multiplicity="(0 -1)" elementType="ig.finanzas.Flujo"
     */
    private Collection flujos;

    /**
     * Constructor de la clase.
     * @param flujos
     * @throws DatoInvalidoExcepcion Si la coleccion de flujos es nula.
     */
    public ManejadorFlujos(Collection flujos) throws DatoInvalidoExcepcion {
        if (flujos == null) {
            throw new DatoInvalidoExcepcion("La coleccion de flujos especificada es nula.");
        }
        this.flujos = flujos;
    }

    /**
     * Devuelve el conjunto de objetos de tipo Flujo que hacen parte del manejador.
     * @return
     */
    public Collection getColeccionFlujos() {
        return flujos;
    }

    /**Devuelve un manejador de flujos con los flujos comprendidos entre dos fechas especificadas.
     * En caso de no especificar la fecha de fin del intervalo, se entiende como que lo que se quiere
     * todos los flujos a partir o desde la fecha inicial.
     * @param fechaInicio
     * @param fechaInicioCerrada Especifica si la fecha inicial debe ser incluida o no en el calculo.
     * @param fechaFin
     * @param fechaFinCerrada Especifica si la fecha final debe ser incluida o no en el calculo.
     * @return
     * @throws DatoInvalidoExcepcion Si la fecha de inicio es nula.*/
    public ManejadorFlujos flujosEntre(Date fechaInicio, boolean fechaInicioCerrada, Date fechaFin, boolean fechaFinCerrada)
            throws DatoInvalidoExcepcion {
        if (fechaFin == null) {
            return flujosDesde(fechaInicio, fechaInicioCerrada);
        }
        if (fechaInicio == null) {
            throw new DatoInvalidoExcepcion("La fecha de inicio es nula.");
        }
        Collection cumplen = new ArrayList();
        Flujo flujo;
        boolean condicion;
        Collections.sort((List) flujos);
        Iterator i = flujos.iterator();
        while (i.hasNext()) {
            flujo = (Flujo) i.next();
            if (fechaInicioCerrada) {
                condicion = (fechaInicio.getTime() <= flujo.getFecha().getTime());
            } else {
                condicion = (fechaInicio.getTime() < flujo.getFecha().getTime());
            }

            if (fechaFinCerrada) {
                condicion = condicion && (flujo.getFecha().getTime() <= fechaFin.getTime());
            } else {
                condicion = condicion && (flujo.getFecha().getTime() < fechaFin.getTime());
            }

            if (condicion) {
                cumplen.add(flujo);
            }
        }
        return new ManejadorFlujos(cumplen);
    }

    /**
     * Devuelve todos los flujos a partir de una fecha inicial.
     * @param fechaInicio
     * @param fechaInicioCerrada Especifica si la fecha inicial debe ser incluida o no en el calculo.
     * @return
     * @throws DatoInvalidoExcepcion Si la fecha de inicio es nula.
     */
    public ManejadorFlujos flujosDesde(Date fechaInicio, boolean fechaInicioCerrada) throws DatoInvalidoExcepcion {

        if (fechaInicio == null) {
            throw new DatoInvalidoExcepcion("La fecha de inicio es nula.");
        }
        Collection cumplen = new ArrayList();
        Flujo flujo;
        boolean condicion;
        Collections.sort((List) flujos);
        Iterator i = flujos.iterator();
        while (i.hasNext()) {
            flujo = (Flujo) i.next();

            if (fechaInicioCerrada) {
                condicion = (fechaInicio.getTime() <= flujo.getFecha().getTime());
            } else {
                condicion = (fechaInicio.getTime() < flujo.getFecha().getTime());
            }

            if (condicion) {
                cumplen.add(flujo);
            }
        }

        return new ManejadorFlujos(cumplen);
    }

    /**
     * Adiciona un flujo a la coleccion de flujos que se manejan.
     * @param flujo
     * @throws DatoInvalidoExcepcion Si el flujo especificado es nulo.
     */
    public void adicionarFlujo(Flujo flujo) throws DatoInvalidoExcepcion {
        if (flujo == null) {
            throw new DatoInvalidoExcepcion("El flujo especificado es nulo.");
        }
        flujos.add(flujo);
    }

    /**
     * Devuelve la fecha del primer flujo registrado en el manejador.
     * @return La fecha o null.
     */
    public Date fechaPrimerFlujo() {
        if (flujos.size() == 0) {
            return null;
        }
        Collections.sort((List) flujos);
        return (Date) ((Flujo) ((List) flujos).get(0)).getFecha().clone();
    }

    /**
     * Devuelve la fecha del ultimo flujo registrado en el manejador.
     * @return La fecha o null.
     */
    public Date fechaUltimoFlujo() throws DatoInvalidoExcepcion {
        if (flujos.size() == 0) {
            throw new DatoInvalidoExcepcion("No hay flujos");
            //return null;
        }
        Collections.sort((List) flujos);
        return (Date) ((Flujo) ((List) flujos).get(flujos.size() - 1)).getFecha().clone();
    }

    /**
     * Devuelve la fecha de repreciacion de los flujos del manejador. La
     * fecha de repreciacion esta definida como la ultima fecha del manejador
     * para la que existe un flujo conocido. Si dentro de la coleccion no existe
     * ningun flujo conocido entonces devuelve null.
     * @param activa Define si la fecha se calcula para la parte activa, en caso contrario
     * se calcula para la parte pasiva.
     * @return La fecha o null.
     */
    public Date fechaRepreciacion(boolean activa) {
        if (flujos.size() == 0) {
            return null;
        }
        Flujo ultimoflujoconocido = null;
        boolean iterandoflujosconocidos = true;

        Collections.sort((List) flujos);
        Iterator i = flujos.iterator();
        while (i.hasNext() && iterandoflujosconocidos == true) {

            Flujo flujoactual = (Flujo) i.next();
            if ((flujoactual.getTotal() >= 0) == activa) {
                if (flujoactual.esConocido() == true) {
                    ultimoflujoconocido = flujoactual;
                } else {
                    iterandoflujosconocidos = false;
                }
            }

        }

        if (ultimoflujoconocido == null) {
            return null;
        }
        return ultimoflujoconocido.getFecha();
    }

    /**
     * Devuelve el numero de flujos contenidos en el manejador.
     * @return
     */
    public int numeroFlujos() {
        return flujos.size();
    }
    //TODO revisar esta vaina
    public Flujo flujoAnteriorA(Date fecha) throws DatoInvalidoExcepcion {
        if (fecha == null) {
            throw new DatoInvalidoExcepcion("La fecha especificada es nula.");
        }
        Flujo anterior = null;
        boolean mayores = false;

        Iterator i = flujos.iterator();
        while (i.hasNext() && mayores == false) {
            Flujo flujo = (Flujo) i.next();

            if (flujo.getFecha().getTime() <= fecha.getTime()) {
                anterior = flujo;
            } else {
                mayores = true;
            }
        }

        return anterior;
    }

    public Flujo flujoAFecha(Date fecha) throws DatoInvalidoExcepcion {

        if (fecha == null) {
            throw new DatoInvalidoExcepcion("Fecha especificada es nula.");
        }
        Iterator i = flujos.iterator();
        while (i.hasNext()) {
            Flujo flujo = (Flujo) i.next();

            if (flujo.getFecha().getTime() == fecha.getTime()) {
                return flujo;
            }
        }
        return null;
    }

    /**
     * Calcula la tasa interna de retorno (TIR) al conjunto de flujos. Si el resultado de la tir
     * es mayor que 10 (tir = 1000%) o menor que -10 (tir=-1000) el valor que devuelve la funcion es
     * de 10 y -10 respectivamente. Cuando no se tienen suficientes valores para el calculo de la
     * tir se devuelve el valor de 0.
     * @return
     * @throws DatoInvalidoExcepcion Si no existen flujos en el manejador.
     */
    public double tir() throws DatoInvalidoExcepcion {
        try {
            return LibreriaOperacionesFinancieras.tir(flujos);
        } catch (LogicaExcepcion e) {
            /*
            Este caso se presenta cuando no hay suficientes valores para calcular la tir.
            En terminos generales sucede cuando el numero de flujos del manejador es 1,
            como no es suficiente informacion para el calculo de la TIR se devuelve 0.
             */
            return 0;
        }
    }

    /**
     * Descuenta los flujos del manejador a una fecha y tasa especifica.
     * @param tasa
     * @param fecha
     * @return
     * @throws DatoInvalidoExcepcion Si la tasa o fecha es nula, o si el valor de la tasa en esa fecha es menor o igual a -1.
     * @throws LogicaExcepcion Si no se puede determinar el valor de la tasa en la fecha especificada.
     */
    public double valorDescontado(Tasa tasa, Date fecha) throws DatoInvalidoExcepcion, LogicaExcepcion {
        if (tasa == null) {
            throw new DatoInvalidoExcepcion("La tasa especificada es nula.");
        }
        if (fecha == null) {
            throw new DatoInvalidoExcepcion("La fecha especifiada es nula.");
        }
        double total = 0;

        Iterator i = flujos.iterator();
//        int dia=24*60*60*1000;
        Double tasass=null;
        try {
            while (i.hasNext()) {
                Flujo flujo = (Flujo) i.next();
                int numdias = calcualarDias(fecha, flujo.getFecha());

                if(tasa instanceof TasaFactorDescuento ){
                    tasass = tasass==null?tasa.obtenerValorVigenteA(fecha, numdias):tasass;
                }else{

                    /*
                     * Modificacion Por Cristhiam Reina
                     * Modificado el 18/07/2011
                     */


                    //tasass = tasa.obtenerValorVigenteA(fecha, numdias);

                    if(tasa.getTipo().equals(tasa.TIPO_PLAZOS)){
                        tasass = tasa.obtenerValorVigentePlazoA(fecha, numdias);
                    }
                    else{
                        tasass = tasa.obtenerValorVigenteA(fecha, numdias);
                    }

                    //tasa.obtenerValorVigenteA(fecha, numdias);
                }
                double valorflujo = flujo.valorDescontado(tasass, 365, fecha);
                total += valorflujo;
            }
        } catch (RemoteException e) {
        }
        return total;
    }

    /**
     * Calcula la duracion de la coleccion de flujos. Este metodo utiliza la formula de
     * duracion = sumatoria( valorpresentedelflujo * numdias ) / sumatoria (valorpresentedelflujo).
     * Nota: El resultado de este metodo solo es consistente cuando se opera sobre una coleccion
     * de flujos obtenida de ejecutar flujosParaDuracion de la clase ContratoBean.
     * @param fecha
     * @param tasa
     * @return
     * @throws DatoInvalidoExcepcion Si la tasa o fecha son nulas, o si el valor de la tasa en esa fecha es menor o igual a -1.
     * @throws LogicaExcepcion Si no se puede determinar el valor de la tasa en dicha fecha.
     */
    public double duracion(Date fecha, Tasa tasa) throws DatoInvalidoExcepcion, LogicaExcepcion {

        if (fecha == null) {
            throw new DatoInvalidoExcepcion("La fecha especificada es nula");
        }
        if (tasa == null) {
            throw new DatoInvalidoExcepcion("La tasa especificada es nula");
        }
        double productoactivos = 0, productopasivos = 0;
        double vpactivos = 0, vppasivos = 0;

        Iterator i = flujos.iterator();
        while (i.hasNext()) {

            Flujo flujo = (Flujo) i.next();
//            int numdias = (int)((flujo.getFecha().getTime()-fecha.getTime())/(1000*60*60*24));
            int numdias = calcualarDias(fecha, flujo.getFecha());
            double vpparcial = 0;
            try {
                /*
                 * Modificado por Cristhiam
                 */
                //vpparcial = flujo.valorDescontado(tasa.obtenerValorVigenteA(fecha, numdias), 365, fecha);

                double valTasa = (!tasa.getTipo().equals(tasa.TIPO_PLAZOS))
                                    ? tasa.obtenerValorVigenteA(fecha, numdias) : tasa.obtenerValorVigentePlazoA(fecha, numdias);

                vpparcial = flujo.valorDescontado(valTasa, 365, fecha);
            } catch (RemoteException e) {
            }

            if (vpparcial >= 0) {
                productoactivos += numdias * vpparcial;
                vpactivos += vpparcial;
            } else {
                vpparcial = Math.abs(vpparcial);
                productopasivos += numdias * vpparcial;
                vppasivos += vpparcial;
            }

        }

        if (vppasivos == 0 && vpactivos != 0) {
            return productoactivos / vpactivos / 365;
        } else if (vpactivos != 0 && vppasivos != 0) {
            //Hay parte pasiva
            double duracionactiva = productoactivos / vpactivos / 365;
            double duracionpasiva = productopasivos / vppasivos / 365;
            return duracionactiva - duracionpasiva * vppasivos / vpactivos;
        } else {
            return 0;
        }
    }

    /**
     * Calcula la suma total de los flujos positivos de un objeto Flujo
     * @return
     * //@throws DatoInvalidoExcepcion Si la tasa o fecha es nula, o si el valor de la tasa en esa fecha es menor o igual a -1.
     * //@throws LogicaExcepcion Si no se puede determinar el valor de la tasa en la fecha especificada.
     */
    public double sumaFlujosPositivos() throws DatoInvalidoExcepcion, LogicaExcepcion {

        double total = 0;
        Iterator i = flujos.iterator();
        while (i.hasNext()) {
            Flujo flujo = (Flujo) i.next();
            double valorflujo = flujo.getTotal();
            if (valorflujo > 0) {
                total += valorflujo;
            }
        }

        return total;
    }

    public static void main(String[] arg) {
        java.util.Date _fechaInicial = new Date(java.sql.Timestamp.valueOf("2008-01-01 00:00:00.0").getTime());
        java.util.Date _fechaFinal = new Date(java.sql.Timestamp.valueOf("2008-12-31 00:00:00.0").getTime());

        System.out.println(calcualarDias(_fechaInicial, _fechaFinal));

    /*System.out.println((_fechaFinal.getTime()-_fechaInicial.getTime())/(24*60*60*1000));
    
    _fechaInicial=new Date(java.sql.Timestamp.valueOf("2009-01-01 00:00:00.0").getTime());
    _fechaFinal=new Date(java.sql.Timestamp.valueOf("2009-12-31 00:00:00.0").getTime());
    
    System.out.println((_fechaFinal.getTime()-_fechaInicial.getTime())/(24*60*60*1000));
    
    _fechaInicial=new Date(java.sql.Timestamp.valueOf("2008-01-01 00:00:00.0").getTime());
    _fechaFinal=new Date(java.sql.Timestamp.valueOf("2009-12-31 00:00:00.0").getTime());
    
    System.out.println((_fechaFinal.getTime()-_fechaInicial.getTime())/(24*60*60*1000));*/


    }

    public static Integer calcualarDias(java.util.Date _fechaInicial, java.util.Date _fechaFinal) {
        java.sql.Date _fechaInicialTMP = new java.sql.Date(_fechaInicial.getTime());
        java.sql.Date _fechaFinalTMP = new java.sql.Date(_fechaFinal.getTime());

        GregorianCalendar fechaInicial = new GregorianCalendar();

        fechaInicial.setTimeInMillis(_fechaInicialTMP.getTime());
        fechaInicial.set(GregorianCalendar.HOUR_OF_DAY, fechaInicial.get(GregorianCalendar.HOUR_OF_DAY) * -1);
        fechaInicial.set(GregorianCalendar.SECOND, fechaInicial.get(GregorianCalendar.HOUR_OF_DAY) * -1);
        fechaInicial.set(GregorianCalendar.MINUTE, fechaInicial.get(GregorianCalendar.HOUR_OF_DAY) * -1);
        fechaInicial.set(GregorianCalendar.MILLISECOND, fechaInicial.get(GregorianCalendar.MILLISECOND) * -1);

        GregorianCalendar fechaFinal = new GregorianCalendar();
        fechaFinal.setTimeInMillis(_fechaFinalTMP.getTime());
        fechaFinal.set(GregorianCalendar.HOUR_OF_DAY, fechaFinal.get(GregorianCalendar.HOUR_OF_DAY) * -1);
        fechaFinal.set(GregorianCalendar.SECOND, fechaFinal.get(GregorianCalendar.SECOND) * -1);
        fechaFinal.set(GregorianCalendar.MINUTE, fechaFinal.get(GregorianCalendar.MINUTE) * -1);
        fechaFinal.set(GregorianCalendar.MILLISECOND, fechaFinal.get(GregorianCalendar.MILLISECOND) * -1);

        GregorianCalendar init = new GregorianCalendar();
        init.set(1980, 1, 29, 0, 0, 0);
        int conta = 0;
        while (init.getTimeInMillis() <= fechaFinal.getTimeInMillis()) {
            if (init.getTimeInMillis() >= fechaInicial.getTimeInMillis()) {
                conta++;
            }
            init.add(GregorianCalendar.YEAR, 4);
        }

        int dia = 24 * 60 * 60 * 1000;
//        System.out.println(fechaFinal.getTimeInMillis()-fechaInicial.getTimeInMillis());
        return new Long((((fechaFinal.getTimeInMillis() - fechaInicial.getTimeInMillis()) / dia) - conta)).intValue();
    }

    public double expocision(Date fecha, Tasa tasa) throws DatoInvalidoExcepcion, LogicaExcepcion {

        if (fecha == null) {
            throw new DatoInvalidoExcepcion("La fecha especificada es nula");
        }
        if (tasa == null) {
            throw new DatoInvalidoExcepcion("La tasa especificada es nula");
        }
        double productoactivos = 0, productopasivos = 0;
        double vpactivos = 0, vppasivos = 0;

        Iterator i = flujos.iterator();

        double exposicion = 0;
        while (i.hasNext()) {

            Flujo flujo = (Flujo) i.next();
//            int numdias = (int)((flujo.getFecha().getTime()-fecha.getTime())/(1000*60*60*24));
            int numdias = calcualarDias(fecha, flujo.getFecha());
            double vpparcial = 0;
            try {

                double valTasa = (!tasa.getTipo().equals(tasa.TIPO_PLAZOS))
                                    ? tasa.obtenerValorVigenteA(fecha, numdias) : tasa.obtenerValorVigentePlazoA(fecha, numdias);

                vpparcial = flujo.valorDescontado(valTasa, 365, fecha);


                double duracion = ((double)numdias)/((double)365);
                double exp_flujo = vpparcial * (duracion/(valTasa+1));
                exposicion += exp_flujo;
                //formula de ivan
            } catch (RemoteException e) {
            }

        }


            return exposicion;

    }
}

